package com.atguigu.gmall.cart.service.impl;

import com.atguigu.gmall.cart.mapper.CartInfoMapper;
import com.atguigu.gmall.cart.service.CartService;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.model.cart.CarInfoVo;
import com.atguigu.gmall.model.cart.CartInfo;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author atguigu-mqx
 */
@Service
public class CartServiceImpl implements CartService {

    //  服务层引入mapper 层 有关于数据库表 cartInfo
    @Autowired
    private CartInfoMapper cartInfoMapper;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addToCart(Long skuId, String userId, Integer skuNum) {
        /*
            1.  查看购物车中，是否有该商品
                true:
                    商品数量相加
                false:
                    直接数据
            2.  同步缓存，想缓存中添加数据！
         */
        //  添加数据到数据库之前，我们先判断缓存中是否有当前购物车的key
        String cartKey = getCartKey(userId);
        //  如果缓存中没有当前用户购物车的key
        if (!redisTemplate.hasKey(cartKey)) {
            //  点击添加商品的时候，缓存一定是有数据的！
            this.loadCartToCache(userId);
        }

        //  select * from cart_info where user_id = ? and sku_id = ?
        //        QueryWrapper<CartInfo> queryWrapper = new QueryWrapper<>();
        //        queryWrapper.eq("user_id",userId);
        //        queryWrapper.eq("sku_id",skuId);
        //        CartInfo cartInfoExist = cartInfoMapper.selectOne(queryWrapper);

        //  表示从缓存中的购物车获取数据！
        CartInfo cartInfoExist = (CartInfo) this.redisTemplate.opsForHash().get(cartKey,skuId.toString());
        //  说明购物车中有该商品
        if (cartInfoExist!=null){
            //  商品数量相加
            cartInfoExist.setSkuNum(cartInfoExist.getSkuNum()+skuNum);
            //  赋值实时价格
            //  cartInfoExist.getCartPrice() 表示第一件商品加入的价格
            //  什么是实时价格；就是skuInfo 表中的price
            cartInfoExist.setSkuPrice(productFeignClient.getSkuPrice(skuId));
            //  设置复选状态！一开始进来checked = 1 ，通过页面修改成 0 ，添加一件商品 1
            cartInfoExist.setIsChecked(1);
            //  设置添加的时间在第一次添加的时候已经赋值了。但是我们需要处理的是更新时间！
            cartInfoExist.setUpdateTime(new Timestamp(new Date().getTime()));
            //  更新数据：
            cartInfoMapper.updateById(cartInfoExist);

            //  更新 操作缓存 redisTemplate

        }else {
            //  第一次添加购物车，直接将数据添加到数据库
            SkuInfo skuInfo = this.productFeignClient.getSkuInfo(skuId);

            CartInfo cartInfo = new CartInfo();
            //  给cartInfo 赋值
            cartInfo.setUserId(userId);
            cartInfo.setSkuId(skuId);
            cartInfo.setSkuNum(skuNum);
            cartInfo.setCartPrice(skuInfo.getPrice());
            cartInfo.setSkuPrice(skuInfo.getPrice());
            cartInfo.setImgUrl(skuInfo.getSkuDefaultImg());
            cartInfo.setSkuName(skuInfo.getSkuName());
            cartInfo.setCreateTime(new Timestamp(new Date().getTime()));
            cartInfo.setUpdateTime(new Timestamp(new Date().getTime()));
            //  插入数据库！
            cartInfoMapper.insert(cartInfo);
            //  废物再利用
            cartInfoExist = cartInfo;
            //  也需要操作缓存
        }

        //  有关于购物车缓存 redis  和  mysql 的操作问题！
        //  查询数据的时候，先看缓存，如果缓存没有，再看数据库并将数据load 到缓存中！
        //  购物车：再添加购物车时候，同时放入缓存一份！ insert into mysql and redis!

        //  向缓存放入数据
        redisTemplate.opsForHash().put(cartKey,skuId.toString(),cartInfoExist);
        //  设置一个过期时间
        setCartKeyExpire(cartKey);
    }

    /**
     * 查看购物车列表
     * @param userId    登录用户Id
     * @param userTempId    未登录的用户Id
     * @return
     */
    @Override
    public List<CartInfo> getCartList(String userId, String userTempId) {
        //  声明一个购物车集合来存储数据
        List<CartInfo> cartInfoList = new ArrayList<>();
        //  情况一：只有userId, 情况二：只有userTempId,情况三：两个都有{合并购物车}
        //  判断
        if (StringUtils.isEmpty(userId)) {
            //  未登录情况
            cartInfoList = getCartList(userTempId);
        }
        //  判断
        if (!StringUtils.isEmpty(userId)) {
            //  登录情况
            if (StringUtils.isEmpty(userTempId)){
                //  返回登录购物车数据
                cartInfoList = getCartList(userId);
                //  返回数据
                return cartInfoList;
            }
            //  获取未登录购物车数据
            List<CartInfo> noLoginCartList = this.getCartList(userTempId);
            if (!CollectionUtils.isEmpty(noLoginCartList)){
                //  合并购物车
                cartInfoList = this.mergeCartInfoList(noLoginCartList,userId);
                //  删除未登录购物车数据 购物车数据，在缓存，mysql 中都有！
                this.deleteCartList(userTempId);
            }else {
                //  返回登录购物车数据
                cartInfoList = getCartList(userId);
            }
        }
        //  返回购物车列表
        return cartInfoList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void checkCart(Long skuId, Integer isChecked, String userId) {
        //  修改两个地方 redis，mysql！ skuId userId 在cartInfo 表中应该是唯一！
        //  update cart_info set is_checked = ? where sku_id= ? and user_id = ?
        CartInfo cartInfo = new CartInfo();
        cartInfo.setIsChecked(isChecked);
        cartInfo.setUpdateTime(new Timestamp(new Date().getTime()));
        //        QueryWrapper<CartInfo> queryWrapper = new QueryWrapper<>();
        //        queryWrapper.eq("sku_id",skuId);
        //        queryWrapper.eq("user_id",userId);

        UpdateWrapper<CartInfo> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("sku_id",skuId);
        updateWrapper.eq("user_id",userId);

        cartInfoMapper.update(cartInfo,updateWrapper);

        //  处理redis
        //  先获取缓存的key
        String cartKey = this.getCartKey(userId);
        //  hget key field;
        CartInfo cartInfo1 = (CartInfo) this.redisTemplate.opsForHash().get(cartKey, skuId.toString());

        //  判断这个对象是否为空
        if (cartInfo1!=null) {
            //  赋值操作
            cartInfo1.setIsChecked(isChecked);
            cartInfo1.setUpdateTime(new Timestamp(new Date().getTime()));
            //  将这个对象写入缓存
            this.redisTemplate.opsForHash().put(cartKey,skuId.toString(),cartInfo1);
        }
        //  可以给一个过期时间
        this.setCartKeyExpire(cartKey);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteCart(Long skuId, String userId) {
        //  删除mysql
        //  delete from cart_info where sku_id = ? and user_id = ?
        cartInfoMapper.delete(new QueryWrapper<CartInfo>().eq("sku_id",skuId).eq("user_id",userId));
        //  再删除redis
        //  获取到缓存的key
        String cartKey = this.getCartKey(userId);

        //  Long delete = this.redisTemplate.opsForHash().delete(cartKey, skuId.toString());
        //  Long delete = this.redisTemplate.boundHashOps(cartKey).delete(skuId.toString());

        //  判断这个大购物车key 中是否有小的skuId.toString
        Boolean flag = this.redisTemplate.boundHashOps(cartKey).hasKey(skuId.toString());
        //  当前这个购物车中有这个商品
        if (flag){
            this.redisTemplate.boundHashOps(cartKey).delete(skuId.toString());
        }
    }

    @Override
    public List<CartInfo> getCartCheckedList(String userId) {
        //  直接从缓存中获取数据！
        //  组成缓存的key
        String cartKey = this.getCartKey(userId);
        //  获取购物车数据 hvals key
        List<CartInfo> cartInfoList = this.redisTemplate.boundHashOps(cartKey).values();

        //        List<CartInfo> cartInfos = new ArrayList<>();
        //
        //        //  获取选中的数据
        //        for (CartInfo cartInfo : cartInfoList) {
        //            if (cartInfo.getIsChecked().intValue()==1) {
        //                cartInfos.add(cartInfo);
        //            }
        //        }

        List<CartInfo> cartInfos = cartInfoList.stream().filter((cartInfo) -> {
            return cartInfo.getIsChecked().intValue() == 1;
        }).collect(Collectors.toList());
        //  返回数据！
        return cartInfos;
    }

    /**
     * 删除未登录购物车数据
     * @param userTempId
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteCartList(String userTempId) {
        //  删除购物车：  缓存一份，数据库一份！
        //  删除数据库：
        cartInfoMapper.delete(new QueryWrapper<CartInfo>().eq("user_id",userTempId));
        //  删除缓存：
        String cartKey = this.getCartKey(userTempId);
        //  返回true 或者是 false!
        //  方式一：
        //  this.redisTemplate.delete(cartKey);
        //  先判断是否有这个key
        //  方式二：
        if (redisTemplate.hasKey(cartKey)){
            this.redisTemplate.delete(cartKey);
        }
    }

    /**
     * 合并购物车数据
     * @param noLoginCartList
     * @param userId
     * @return
     */
    private List<CartInfo> mergeCartInfoList(List<CartInfo> noLoginCartList, String userId) {
        //  未登录-->登录 合并条件是什么? skuId
        /*
        demo1:
        登录：
            37 1
            38 1
        未登录：
            37 1
            38 1
            39 1
        合并之后的数据
            37 2
            38 2
            39 1
         */
        //  获取登录的集合数据，根据userId 查询
        List<CartInfo> logInCartList = this.getCartList(userId);
        //  方案一：双重for 进行遍历比较skuId
        //        for (CartInfo cartInfo : noLoginCartList) {
        //            for (CartInfo info : logInCartList) {
        //                //  相同
        //                if (cartInfo.getSkuId().equals(info.getSkuId())){
        //                    //  数量相加
        //                }else {
        //                    //  不同  直接插入数据
        //                }
        //            }
        //        }
        //  第二种方案： 将某个集合变为 map 集合 key = skuId value = cartInfo;
        //  未登录的集合 noLoginCartList
        Map<Long, CartInfo> longCartInfoMap = logInCartList.stream().collect(Collectors.toMap(CartInfo::getSkuId, cartInfo -> cartInfo));
        //        Map<Long, CartInfo> map = new HashMap<>();
        //        for (CartInfo cartInfo : logInCartList) {
        //            map.put(cartInfo.getSkuId(),cartInfo);
        //        }
        //  循环遍历 将未登录购物车放在外层循环上！
        for (CartInfo cartInfo : noLoginCartList) {
            //  判断是否包含skuId 处理  37,38 两个商品！
            if (longCartInfoMap.containsKey(cartInfo.getSkuId())){
                //  数量相加
                //  获取登录的购物车对象
                CartInfo loginCartInfo = longCartInfoMap.get(cartInfo.getSkuId());
                loginCartInfo.setSkuNum(loginCartInfo.getSkuNum()+cartInfo.getSkuNum());
                //  合并完了，还要更新一下updateTime
                loginCartInfo.setUpdateTime(new Timestamp(new Date().getTime()));
                //  细节处理：
                if (cartInfo.getIsChecked().intValue()==1){
                    //  处理数据库中购物项的状态为 0 的时候，将其改为1！
                    if (loginCartInfo.getIsChecked().intValue()==0){
                        loginCartInfo.setIsChecked(1);
                    }
                    //  如果你不区分是0或者是1 统统赋值
                    //  loginCartInfo.setIsChecked(1);
                }
                //  操作数据库！
                cartInfoMapper.updateById(loginCartInfo);
            }else {
                //  39
                //  细节： 修改userId,将
                cartInfo.setUserId(userId);
                cartInfo.setCreateTime(new Timestamp(new Date().getTime()));
                cartInfo.setUpdateTime(new Timestamp(new Date().getTime()));
                cartInfoMapper.insert(cartInfo);
            }
        }

        //  第一个问题，写完了么？  第二个问题，是否需要判断登录集合为空? 不用判断！
        //  汇总数据    37,38,39
        List<CartInfo> cartInfoList = this.loadCartToCache(userId);
        //  返回集合数据
        return cartInfoList;
    }

    /**
     * 根据用户Id 查询购物车列表
     * @param userId {有可能是登录的，也有可能是未登录的}
     * @return
     */
    private List<CartInfo> getCartList(String userId) {
        //  声明一个集合
        List<CartInfo> cartInfoList = new ArrayList<>();
        //  查看购物车列表： 数据存储方式 {redis,mysql} 在添加购物车的时候，直接将数据放入的缓存!
        if (StringUtils.isEmpty(userId)) {
            return cartInfoList;
        }
        //  先获取缓存的数据
        String cartKey = this.getCartKey(userId);
        //  缓存： hash hset key field value   hget key field  hvals key!
        //  cartInfoList = this.redisTemplate.boundHashOps(cartKey).values();
        cartInfoList = this.redisTemplate.opsForHash().values(cartKey);
        //  判断这个集合是否为空 显示的时候是有顺序而言的！ 安装更新时间进行的排序！
        if (!CollectionUtils.isEmpty(cartInfoList)) {
            cartInfoList.sort((o1,o2)->{
                //  安装更新时间进行比较
                return DateUtil.truncatedCompareTo(o2.getUpdateTime(),o1.getUpdateTime(), Calendar.SECOND);
            });
        }else {
            //  获取数据库中的数据，并加载到缓存！
            cartInfoList = this.loadCartToCache(userId);
        }
        //  返回购物车列表
        return cartInfoList;
    }

    /**
     * 查询购物车列表
     * @param userId
     * @return
     */
    public List<CartInfo> loadCartToCache(String userId) {
        //  声明一个集合
        List<CartInfo> cartInfoList = new ArrayList<>();
        //  根据用户Id 查询购物车列表
        QueryWrapper<CartInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id",userId);
        queryWrapper.orderByDesc("update_time");
        cartInfoList = cartInfoMapper.selectList(queryWrapper);
        //  判断这个集合
        if (CollectionUtils.isEmpty(cartInfoList)) {
            return cartInfoList;
        }
        //  获取缓存的key
        String cartKey = this.getCartKey(userId);
        //  hset key field value  | hmset key map
        HashMap<String, Object> map = new HashMap<>();
        //  有数据的话， 将这个数据放入缓存！
        for (CartInfo cartInfo : cartInfoList) {
            //  细节： 查询购物车的时候： 有个字段 实时价格并不在数据库中！
            cartInfo.setSkuPrice(productFeignClient.getSkuPrice(cartInfo.getSkuId()));
            //  this.redisTemplate.opsForHash().put(cartKey,cartInfo.getSkuId().toString(),cartInfo);
            map.put(cartInfo.getSkuId().toString(),cartInfo);
        }
        //  将map 直接放入缓存
        this.redisTemplate.opsForHash().putAll(cartKey,map);

        //  重新给购物车的key 设置一个过期时间
        this.setCartKeyExpire(cartKey);
        //  返回集合数据
        return cartInfoList;
    }

    //  设置过期时间
    private void setCartKeyExpire(String cartKey) {
        //  设置一个过期时间
        redisTemplate.expire(cartKey, RedisConst.USER_CART_EXPIRE, TimeUnit.SECONDS);
    }
    //  获取购物车的key！
    private String getCartKey(String userId) {
        //  缓存的类型选中谁? hash  hset key field value   hget key field;
        //  key = user:userId:cart  field = skuId  value = cartInfo;
        return RedisConst.USER_KEY_PREFIX+userId+RedisConst.USER_CART_KEY_SUFFIX;
    }
}
